using System;
using System.Collections.Generic;
using System.Text;
using MonoTouch.Foundation;

namespace Ro.Norc.ServiceStackClient
{
	/// <summary>
	/// The main client class. It handles the calls to the webservice and fires the events when the response arrives
	/// </summary>
	/// <typeparam name="TResponse">
	/// The type of the response that the webservice call returns
	/// </typeparam>
	public class Service<TResponse>
	{
		#region Delegates
		
		/// <summary>
		/// The underlying delegate for the ServiceResult event
		/// </summary>
		public delegate void ServiceResultDelegate(object sender, ServiceResultArgs<TResponse> args);
		
		/// <summary>
		/// The underlying delegate for the ServiceError event
		/// </summary>
		public delegate void ServiceErrorDelegate(object sender, ServiceErrorArgs args);
		
		/// <summary>
		/// The underlying delegate for the ServiceProgress event
		/// </summary>
		public delegate void ServiceProgressDelegate(object sender, ServiceProgressArgs args);
		
		/// <summary>
		/// The underlying delegate for the ServiceUploadProgress event
		/// </summary>
		public delegate void ServiceUploadProgressDelegate(object sender, ServiceProgressArgs args);

		/// <summary>
		/// The underlying delegate for the BasicHttpAuthPrompt event
		/// </summary>
		public delegate void ServiceBasicHttpAuthPromptDelegate(object sender, ServiceBasicHttpAuthPromptArgs args);
		
		#endregion
		
		#region Events
		
		/// <summary>
		/// Occurs when an error occurs while processing the current request
		/// </summary>
		public event ServiceErrorDelegate ServiceError;
		
		/// <summary>
		/// Occurs when the current request is carried out successfully
		/// </summary>
		public event ServiceResultDelegate ServiceResult;
		
		/// <summary>
		/// Occurs when a new data batch is received from the server and provides information about the total progress of the download phase
		/// </summary>
		public event ServiceProgressDelegate ServiceProgress;
		
		/// <summary>
		/// Occurs when a new data batch is sent to the server and provides information about the total progress of the upload phase
		/// </summary>
		public event ServiceUploadProgressDelegate ServiceUploadProgress;
		
		/// <summary>
		/// Occurs when an authentication challenge is received from the server, allowing the client code to provide the credentials
		/// </summary>
		public event ServiceBasicHttpAuthPromptDelegate BasicHttpAuthPrompt;
		
		#endregion
		
		#region Private variables
		
		/// <summary>
		/// The encoder used to encode the request and decode/deserialize the response
		/// </summary>
		private ServiceEncoderInterface _encoder = null;
		
		/// <summary>
		/// The transport used to send the data
		/// </summary>
		private ServiceTransportInterface _transport = null;
		
		/// <summary>
		/// The type of webservice call that is to be performed
		/// </summary>
		private ServiceCallType _callType;
		
		/// <summary>
		/// The endpoint this service client instance is bound to
		/// </summary>
		private string _endpoint = null;
		
		/// <summary>
		/// Whether the client instance has an active connection or not
		/// </summary>
		private bool _loading = false;
		
		/// <summary>
		/// A flag indicating whether the endpoint URL should be auto-completed with the parts corresponding to various configuration options, such as the encoder used and the service call type
		/// </summary>
		private bool _optAutoAppendEndpointParts = true;
		
		/// <summary>
		/// 
		/// </summary>
		private bool _optAutoAppendRequestName = true;
		
		/// <summary>
		/// Additional state info
		/// </summary>
		private object _state = null;
		
		#endregion
		
		#region Constructors
		
		/// <summary>
		/// Creates a new Service client instance
		/// </summary>
		/// <param name="endpoint">
		/// The endpoint this client instance is bound to
		/// </param>
		public Service(string endpoint) 
		{
			if (string.IsNullOrEmpty(endpoint)) {
				throw new ArgumentNullException("endpoint");
			}
			_endpoint = endpoint;
			_callType = ServiceCallType.CallReply;
		}
		
		#endregion
		
		/// <summary>
		/// Send the given request to the web-service with no additional state info
		/// </summary>
		/// <param name='request'>
		/// The request that should be sent
		/// </param>
		public void Send(object request) 
		{
			Send(request, null);
		}
		
		/// <summary>
		/// Sends the given request to the web-service
		/// </summary>
		/// <param name="request">
		/// The request that should be sent
		/// </param>
		/// <param name="state">
		/// Additional state info that will be provided to the event arguments upon request completion or failure
		/// </param>
		public void Send(object request, object state) 
		{
			ServiceTransportRequest _request = null;
			string _url = null;
			
			//can't send a request while another is still active
			if (_loading) {
				throw new InvalidOperationException("Cannot perform a request while another is still running");
			}
			
			//can't send a request whithout having an encoder
			if (_encoder == null) {
				throw new InvalidOperationException("Cannot perform a request without setting an encoder first");
			}

			//don't allow a one-way call with a response type other than ServiceResultOneWay
			if (_callType.OneWay && typeof(TResponse) != typeof(ServiceResultOneWay)) {
				throw new InvalidOperationException("Cannot perform a one-way call with a response type other than Ro.Norc.ServiceStackClient.ServiceResultOneWay");
			}
			
			//set default transport
			if (Transport == null) {
				Transport = new ServiceTransportNSUrlConnection();
			}
			
			//save state info
			_state = state;
			
			//create url
			_url = _endpoint;
			if (_optAutoAppendEndpointParts) {
				_url = string.Format("{0}/{1}/{2}", _url, _encoder.EndpointPart, _callType.EndpointPart);
			}
			if (_optAutoAppendRequestName) {
				_url = string.Format("{0}/{1}", _url, request.GetType().Name);
			}

			//send the request
			_request = new ServiceTransportRequest() {
				Method = "POST",
				Body = _encoder.Encode(request),
				Url = _url,
				RequestId = Guid.NewGuid().ToString(),
				CallType = _callType
			};
			_request.Headers.Add("Content-Type", _encoder.ContentType);
			_transport.Send(_request);
		}
		
		/// <summary>
		/// Closes the current request and attempts to cancel the underlying HTTP request. No more event notifications will be received after calling this method.
		/// </summary>
		public void Close()
		{
			if (_transport == null) {
				return;
			}
			if (_transport.Loading) {
				_transport.Close();
			}
			_state = null;
		}
		
		/// <summary>
		/// Raises the ServiceResult event
		/// </summary>
		/// <param name="result">
		/// The result of the last request. Will be passed along as event data
		/// </param>
		private void OnResult(TResponse result) 
		{
			try {//catch potential exception thrown by user delegates
				if (ServiceResult != null) {
					ServiceResult(this, new ServiceResultArgs<TResponse>(result, _state));
				}
			} finally {
				_state = null;
			}
		}
		
		/// <summary>
		/// Raises the ServiceError event
		/// </summary>
		/// <param name='error'>
		/// The NSError occured during the last request. Will be passed along as event data
		/// </param>
		private void OnError(NSError error)
		{
			try {//catch potential exception thrown by user delegates
				if (ServiceError != null) {
					ServiceError(this, new ServiceErrorArgs(error, _state));
				}
			} finally {
				_state = null;
			}
		}
		
		/// <summary>
		/// Processes the response received from the underlying ServiceTransportInterface object
		/// </summary>
		private void OnTransportResponse(object sender, ServiceTransportResponseArgs args)
		{
			ServiceTransportResponse _request = args.Response;
			TResponse _resultObj = default(TResponse);
			string _resultStr = null;
			
			if (_request.Error != null) {
				OnError(_request.Error);
				return;
			}
			
			if (_transport.CurrentRequest.CallType.OneWay) {
				OnResult((TResponse)ServiceUtil.GetResultOneWay(_request.StatusCode, _request.ContentType));
				return;
			}
			
			if (!_encoder.IsContentTypeValid(_request.ContentType)) {
				OnError(ServiceUtil.GetError(ServiceErrorCode.ERROR_CONTENT_TYPE_INVALID));
				return;
			}
			
			try {
				_resultStr = Encoding.UTF8.GetString(_request.Body);
				_resultObj = (TResponse)_encoder.Decode(_resultStr, typeof(TResponse));
			} catch (Exception) {
				_resultObj = default(TResponse);
			}
			
			if (EqualityComparer<TResponse>.Default.Equals(_resultObj, default(TResponse))) {
				OnError(ServiceUtil.GetError(ServiceErrorCode.ERROR_CONTENT_PARSE_ERROR));
			} else {
				OnResult(_resultObj);
			}
		}
		
		/// <summary>
		/// handles the progress notification from the underyling ServiceTransportInterface object and raises the ServiceProgress event
		/// </summary>
		private void OnTransportProgress(object sender, ServiceProgressArgs args)
		{
			try {//catch potential exception thrown by user delegates
				if (ServiceProgress != null) {
					ServiceProgress(this, new ServiceProgressArgs(args.Progress));
				}
			} finally {
				//
			}
		}
		
		/// <summary>
		/// Handles the upload progress notification from the underyling ServiceTransportInterface object and raises the ServiceUploadProgressEvent
		/// </summary>
		private void OnTransportUploadProgress(object sender, ServiceProgressArgs args)
		{
			try {//catch potential exception thrown by user delegates
				if (ServiceUploadProgress != null) {
					ServiceUploadProgress(this, new ServiceProgressArgs(args.Progress));
				}
			} finally {
				//
			}
		}
		
		/// <summary>
		/// Handles the basic http authentication promp notification from the underlying ServiceTransportInterface object raises the BasicHttpAuthPrompt event
		/// </summary>
		private void OnTransportBasicHttpAuthenticationPrompt(object sender, ServiceBasicHttpAuthPromptArgs args)
		{
			try {//catch potential exception thrown by user delegates
				if (BasicHttpAuthPrompt != null) {
					BasicHttpAuthPrompt(this, new ServiceBasicHttpAuthPromptArgs(args.Prompt));
				}
			} finally {
				//
			}
		}
		
		#region Public properties
		
		/// <summary>
		/// Gets or sets the transport adapter used to send and receive the data
		/// </summary>
		public ServiceTransportInterface Transport
		{
			set {
				if (value == null) {
					throw new ArgumentNullException("value", "Assigned ServiceTransportInterface instance cannot be null");
				}

				if (_transport != null) {
					if (_transport.Loading) {
						throw new InvalidOperationException("Cannot change transport while the current one is busy");
					}
					//remove event listeners from previous transport object
					_transport.TransportUploadProgress -= OnTransportUploadProgress;
					_transport.TransportProgress -= OnTransportProgress;
					_transport.TransportResponse -= OnTransportResponse;
					_transport.TransportBasicHttpAuthPrompt -= OnTransportBasicHttpAuthenticationPrompt;
				}

				//attach event listeners to new transport object
				_transport = value;
				_transport.TransportUploadProgress += OnTransportUploadProgress;
				_transport.TransportProgress += OnTransportProgress;
				_transport.TransportResponse += OnTransportResponse;
				_transport.TransportBasicHttpAuthPrompt += OnTransportBasicHttpAuthenticationPrompt;
			}
			get {
				return _transport;
			}
		}
		
		/// <summary>
		/// Gets or sets the endpoint that this client instance is bound to
		/// </summary>
		public string Endpoint
		{
			get {
				return _endpoint;
			}
			set {
				if (string.IsNullOrEmpty(_endpoint))
					throw new ArgumentNullException("value", "Assigned endpoint must be not null and not empty");
				_endpoint = value;
			}
		}
		
		/// <summary>
		/// Gets or sets the encoder used to encode requests and decode responses
		/// </summary>
		public ServiceEncoderInterface Encoder
		{
			get {
				return _encoder;
			}
			set {
				if (value == null) {
					throw new ArgumentNullException("value", "Assigned ServiceEncoderInterface instance cannot be null");
				}
				_encoder = value;
			}
		}
		
		/// <summary>
		/// Gets or sets the type of webservice call that is to be performed
		/// </summary>
		public ServiceCallType CallType
		{
			get {
				return _callType;
			}
			set {
				if (value == null) {
					throw new ArgumentNullException("value", "Assigned ServiceCallType instance cannot be null");
				}
				_callType = value;
			}
		}
		
		/// <summary>
		/// Gets or sets the flag indicating whether the endpoint URL should be auto-completed with the parts corresponding to various configuration options, such as the encoder used and the service call type
		/// </summary>
		public bool OptAutoAppendEndpointParts
		{
			get {
				return _optAutoAppendEndpointParts;
			}
		 	set {
				_optAutoAppendEndpointParts = value;
			}
		}
		
		/// <summary>
		/// 
		/// </summary>
		public bool OptAutoAppendRequestName
		{
			get {
				return _optAutoAppendRequestName;
			}
			set {
				_optAutoAppendRequestName = value;
			}
		}

		/// <summary>
		/// Gets a value indicating whether a connection is currently active
		/// </summary>
		public bool Loading
		{
			get {
				return _loading;
			}
		}
		
		#endregion
	}
}